/*
 * COPYRIGHT. ShenZhen JiMi Technology Co., Ltd. 2017.
 * ALL RIGHTS RESERVED.
 *
 * No part of this publication may be reproduced, stored in a retrieval system, or transmitted,
 * on any form or by any means, electronic, mechanical, photocopying, recording, 
 * or otherwise, without the prior written permission of ShenZhen JiMi Network Technology Co., Ltd.
 *
 * Amendment History:
 * 
 * Date                   By              Description
 * -------------------    -----------     -------------------------------------------
 * 2017年4月24日    li.shangzhi         Create the class
 * http://www.jimilab.com/
 */

package com.jimi.gateway.filter.openapi.route;

import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.RedisTemplate;

import com.alibaba.fastjson.JSONObject;
import com.jimi.app.api.IApiAppFunsApi;
import com.jimi.app.dto.output.ApiAppFunsOutputDto;
import com.jimi.app.dto.output.ApiOpenFunctionsOutputDto;
import com.jimi.framework.context.SpringContextHolder;
import com.jimi.framework.utils.DateUtil;
import com.jimi.gateway.exception.GatewayErrorCode;
import com.jimi.gateway.utils.Constants;
import com.jimi.gateway.utils.ResultModel;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;

/**
 * @FileName RouteLimitFilter.java
 * @Description: 路由前限流，采用redis过期机制控制，使用app_key和method进行秒，天维度控制
 *
 * @Date 2017年4月24日 上午9:27:49
 * @author li.shangzhi
 * @version 1.0
 */
public class RouteLimitFilter extends ZuulFilter {

	private static final int FILTER_ORDER = 0;

	private final Logger logger = LoggerFactory.getLogger(RouteLimitFilter.class);

	@Override
	public boolean shouldFilter() {
		RequestContext ctx = RequestContext.getCurrentContext();
		String routeId = String.valueOf(ctx.get("proxy"));
		if (!Constants.OPEN_API_NAMESPACE.equals(routeId)) {
			return false;
		}
		return ctx.getRouteHost() != null && ctx.sendZuulResponse() && ctx.getBoolean(Constants.IS_NEXT_FILTER, true);
	}

	@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
		ApiOpenFunctionsOutputDto outputDto = (ApiOpenFunctionsOutputDto) ctx.get(Constants.OPEN_FUNCTION);
		if (outputDto.getLimit()) { // 限流开关开启
			HttpServletRequest requeset = ctx.getRequest();
			String method = requeset.getParameter(Constants.METHOD);
			String appKey = requeset.getParameter(Constants.APP_KEY);
			String funId = outputDto.getId();
			IApiAppFunsApi apiAppFunsApi = SpringContextHolder.getBean(IApiAppFunsApi.class);
			ApiAppFunsOutputDto apiAppFunsOutputDto = apiAppFunsApi.getByAppFun(appKey, funId);
			if (null != apiAppFunsOutputDto) {
				// 每秒并发数
				int perSecond = apiAppFunsOutputDto.getReqPerSecond();
				String secondKey = "jimiOpenApi.limitFilter.second:" + appKey + "_" + method;
				boolean isFilter = filterOper(secondKey, perSecond, 1000, TimeUnit.MILLISECONDS);
				if (isFilter) {
					logger.info("【请求限流】用户AppKey：{}，请求方法：{}，每秒请求频率超过{}，限制访问。redis Key:{}", appKey, method, perSecond, secondKey);
					ctx.setSendZuulResponse(false);// 过滤该请求，不对其进行路由
					ctx.setResponseStatusCode(200);// 返回错误码
					ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.REQ_RATE_TOO_HIGH_S_ERROR)));
					ctx.set(Constants.IS_NEXT_FILTER, false);// 后续的过滤器是否继续执行
					return null;
				}
				// 每天请求数
				int perDay = apiAppFunsOutputDto.getReqPerDay();
				// 得到当天
				int curDay = DateUtil.getCurDay();
				String dayKey = "jimiOpenApi.limitFilter.day:" + curDay + "_" + appKey + "_" + method;
				long times = DateUtil.getResidueDay();
				isFilter = filterOper(dayKey, perDay, times, TimeUnit.MILLISECONDS);
				if (isFilter) {
					logger.info("【请求限流】用户AppKey：{}，请求方法：{}，每天请求频率超过{}，限制访问。redis Key:{}", appKey, method, perDay, dayKey);
					ctx.setSendZuulResponse(false);// 过滤该请求，不对其进行路由
					ctx.setResponseStatusCode(200);// 返回错误码
					ctx.setResponseBody(JSONObject.toJSONString(new ResultModel(GatewayErrorCode.REQ_RATE_TOO_HIGH_D_ERROR)));
					ctx.set(Constants.IS_NEXT_FILTER, false);// 后续的过滤器是否继续执行
					return null;
				}
			} else {
				logger.warn("请求方法：{}，查找限流接口失败，app_key：{}，fun_id：{}", method, appKey, funId);
			}
		} else {
			logger.info("请求方法：{}，限流开关：{}", outputDto.getMethod(), outputDto.getLimit());
		}
		ctx.set(Constants.IS_NEXT_FILTER, true);
		return null;
	}

	@Override
	public String filterType() {
		return Constants.ROUTE_FILTER;
	}

	@Override
	public int filterOrder() {
		return FILTER_ORDER;
	}

	private boolean filterOper(String uniqueKey, long limits, long times, TimeUnit unit) {
		if (limits == 0 || times == 0) {
			return false;
		}
		try {
			logger.info("操作的redis Key为：{}", uniqueKey);
			RedisTemplate<String, String> redisTemplate = SpringContextHolder.getBean("redisTemplate");
			BoundZSetOperations<String, String> boundZSetOperations = redisTemplate.boundZSetOps(uniqueKey);
			Set<String> redisStrings = boundZSetOperations.range(0, boundZSetOperations.size());
			AtomicLong count = new AtomicLong(0);
			if (redisStrings != null && redisStrings.size() > 0) {
				Object[] objects = redisStrings.toArray();
				count.set(Long.parseLong(String.valueOf(objects[objects.length - 1])));
			}
			if (count.get() > limits) {
				// 超出限制，开启禁用
				Long expries = boundZSetOperations.getExpire();
				if (expries != null && expries > 0) {
					return true;
				}
			}
			long seq = count.incrementAndGet();
			boundZSetOperations.add(String.valueOf(seq), seq); // 填充并计数
			boundZSetOperations.expire(times, unit); // 设置过期时间
		} catch (Exception e) {
			logger.error("控制api被调用频率redis发生异常:uniqueKey=" + uniqueKey, e);
		}
		return false;
	}
}
